package pers.vic.boot.security.shiro;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.RedisTemplate;

import pers.vic.boot.security.autoconfigure.SecurityProperties;
import pers.vic.boot.security.jwt.JwtService;
import pers.vic.boot.security.redis.RedisService;

/**
 * @description: SHIRO 配置类， 依赖于REDIS
 * @author: Vic.xu
 * @date: 2019年12月31日 下午1:11:41
 */
@Configuration
@ConfigurationProperties(prefix = SecurityProperties.SECURITY_PREFIX)
@ConditionalOnClass(Filter.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnExpression("${auth.shiro.enabled:true}")
@EnableConfigurationProperties({ ShiroLifecycleBeanPostProcessorConfig.class })
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
public class ShiroConfig {
	/*
	 * Autowired无法正常注入的疑难杂症 https://www.lagou.com/lgeduarticle/66756.html\
	 */

	@Autowired
	private SecurityProperties securityProperties;

	/**
	 * jwtService
	 */
	@Bean
	@ConditionalOnBean(RedisTemplate.class)
	@ConditionalOnMissingBean
	public JwtService jwtService(RedisService redisService) {
		return new JwtService(redisService);
	}

	/**
	 * 注册jwtFilter
	 * @return
	 */
	@Bean
	public JwtFilter jwtFilter() {
		return new JwtFilter();
	}

	@Bean
	@ConditionalOnMissingBean
	public MyRealm myRealm(JwtService jwtService) {
		return new MyRealm(jwtService);
	}

	@Bean("registerJwtFilter")
 	@ConditionalOnBean(JwtFilter.class)
	public FilterRegistrationBean<JwtFilter> registerJwtFilter(@Autowired JwtFilter jwtFilter) {
		// 设置jwt filter不自动注册到spring管理的监听器中，防止与shiro filter同级，导致该监听器必定执行
		FilterRegistrationBean<JwtFilter> jwtFilterRegister = new FilterRegistrationBean<>(jwtFilter);
		jwtFilterRegister.setEnabled(false);
		return jwtFilterRegister;
	}

	@Bean("securityManager")
	@ConditionalOnMissingBean
	public DefaultWebSecurityManager getManager(MyRealm realm) {
		DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
		/*
		 * 关闭shiro自带的session，详情见文档
		 * http://shiro.apache.org/session-management.html#SessionManagement-
		 * StatelessApplications%28Sessionless%29
		 */
		DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
		DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
		defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
		subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
		manager.setSubjectDAO(subjectDAO);
		// 使用自己的realm
		manager.setRealm(realm);
		return manager;
	}

	@Bean("shiroFilter")
	@ConditionalOnMissingBean
	public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
		ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

		// 添加自己的过滤器并且取名为jwt
		Map<String, Filter> filterMap = new HashMap<>(16);
		/**
		 * 修改new JwtFilter() 为jwtFilter(), JwtFilter交由spring管理,方便在JwtFilter 中注入
		 * 但是JwtFilter交给Spring管理后，Spring将其注册到filterChain中了，与ShiroFilter同级，
		 * 所以即使设置了filter的order，在shiroFilter完了之后也会经过JwtFilter，从而导致认证请求调用链的异常
		 * 依然把JwtFilter交由Spring管理，但是设置这个bean不要注册到filter调用链中:
		 * 通过FilterRegistrationBean取消JwtFilter的自动注册， 参考 文档
		 * https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-disable-registration-of-a-servlet-or-filter
		 * 
		 */
		filterMap.put("jwt", jwtFilter());
		factoryBean.setFilters(filterMap);
		factoryBean.setSecurityManager(securityManager);
		/*
		 * 自定义url规则 http://shiro.apache.org/web.html#urls-
		 */
		LinkedHashMap<String, String> filterRuleMap = new LinkedHashMap<>();

		// 访问401和404页面不通过我们的Filter
		// 可匿名访问
		filterRuleMap.put("/login", "anon");
		filterRuleMap.put("/test/**", "anon");
		// 验证码
		filterRuleMap.put("/captcha", "anon");
		// 一些静态资源
		filterRuleMap.put("/**/*.js", "anon");
		filterRuleMap.put("/**/*.css", "anon");
		filterRuleMap.put("/**/*.ico", "anon");
		filterRuleMap.put("/img/**", "anon");
		filterRuleMap.put("/imgages/**", "anon");
		filterRuleMap.put("/lib/**", "anon");
		//访问附件attachment
		filterRuleMap.put("/attachment/visit/**", "anon");
		// 加入自定义配置的拦截规则
		Map<String, String> customerRule = securityProperties.getFilteRuleMap();
		filterRuleMap.putAll(customerRule);
		// 所有请求通过我们自己的 也可put "jwt,authc"\
		// 加了authc不用每个requestMapping都加@RequiresAuthentication
		filterRuleMap.put("/**", "jwt,authc");
		//如果重写了/**  则最后加进去
		if(customerRule.containsKey("/**")) {
			filterRuleMap.put("/**", customerRule.get("/**"));
		}
		factoryBean.setFilterChainDefinitionMap(filterRuleMap);
		return factoryBean;
	}

	/**
	 * 下面的代码是添加注解支持
	 */
	// 扫描上下文，寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成)，将这些Advisor应用到所有符合切入点的Bean中
	@Bean
	@ConditionalOnMissingBean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		// 强制使用cglib，防止重复代理和可能引起代理出错的问题
		// https://zhuanlan.zhihu.com/p/29161098
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;
	}

	// 开启shiro aop注解支持
	@Bean
	@ConditionalOnMissingBean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
			DefaultWebSecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
		advisor.setSecurityManager(securityManager);
		return advisor;
	}
}
